//
//  OADLayout5.m
//  OASDK_Works
//
//  Created by ChenJie on 3/15/20.
//  Copyright © 2020 Mist. All rights reserved.
//

#import "OADLayout5.h"

@interface OADLayout5 ()

@property (nonatomic, strong) NSMutableArray<OADLayout5 *> *nodes;
@property (nonatomic, strong) NSMutableDictionary<NSString *, OADLayout5 *> *nodePool;
@property (nonatomic, strong) OADLayout5 *superNode;
@property (nonatomic, strong) OADLayout5 *previousSiblingNode;
@property (nonatomic, assign) CGFloat totalNodeGrow;
@property (nonatomic, assign) CGFloat preparedWidth;
@property (nonatomic, assign) CGFloat preparedHeight;
@property (nonatomic, assign) CGFloat flexWidth;
@property (nonatomic, assign) CGFloat flexHeight;
@property (nonatomic, assign) CGFloat mainAxisTotalFreeWidth;
@property (nonatomic, assign) CGFloat mainAxisTotalFreeHeight;
@property (nonatomic, assign) CGFloat mainAxisTotalFlexWidth;
@property (nonatomic, assign) CGFloat mainAxisTotalFlexHeight;
@property (nonatomic, assign) CGFloat totalNodePreparedBoxWidth;
@property (nonatomic, assign) CGFloat totalNodePreparedBoxHeight;
@property (nonatomic, assign) CGFloat totalNodeBoxWidth;
@property (nonatomic, assign) CGFloat totalNodeBoxHeight;
@property (nonatomic, assign) CGFloat maxNodePreparedBoxWidth;
@property (nonatomic, assign) CGFloat maxNodePreparedBoxHeight;
@property (nonatomic, assign) CGFloat frameLeft;
@property (nonatomic, assign) CGFloat frameTop;
@property (nonatomic, assign) CGFloat frameWidth;
@property (nonatomic, assign) CGFloat frameHeight;

@end

@implementation OADLayout5

#pragma mark - Lifecycle methods

- (instancetype)init {
    self = [super init];
    if (self) {
    }
    return self;
}

#pragma mark - Public methods

- (void)addNode:(OADLayout5 *)node {
    node.superNode = self;
    
    [self.nodes addObject:node];
    if (node.name) {
        self.nodePool[node.name] = node;
    }
    
    self.totalNodeGrow += node.grow;
}

- (OADLayout5 *)nodeWithName:(NSString *)name {
    return !name ? nil : self.nodePool[name];
}

#pragma mark - Private methods

- (CGFloat)rightBoundary {
    return self.frameLeft + self.frameWidth + self.marginRight;
}

- (CGFloat)bottomBoundary {
    return self.frameTop + self.frameHeight + self.marginBottom;
}

- (CGFloat)preparedBoxWidth {
    return self.marginLeft + self.preparedWidth + self.marginRight;
}

- (CGFloat)preparedBoxHeight {
    return self.marginTop + self.preparedHeight + self.marginBottom;
}

- (CGFloat)boxWidth {
    return self.marginLeft + self.frameWidth + self.marginRight;
}

- (CGFloat)boxHeight {
    return self.marginTop + self.frameHeight + self.marginBottom;
}

- (CGFloat)contentWidth {
    return self.frameWidth - self.paddingLeft - self.paddingRight;
}

- (CGFloat)contentHeight {
    return self.frameHeight - self.paddingTop - self.paddingBottom;
}

- (CGFloat)crossAxisOriginWithFreeSpace:(CGFloat)crossAxisFreeSpace {
    if (self.superNode.alignItems == AlignItemsFlexStart) {
        return 0;
    }
    
    if (self.superNode.alignItems == AlignItemsCenter) {
        return crossAxisFreeSpace / 2;
    }
    
    if (self.superNode.alignItems == AlignItemsFlexEnd) {
        return crossAxisFreeSpace;
    }
    
    return 0;
}

- (CGFloat)crossAxisOriginLeft {
    if (self.superNode.direction == DirectionRow) { // not cross axis
        return 0;
    }
    
    CGFloat crossAxisFreeSpace = [self.superNode contentWidth] - [self boxWidth];
    return [self crossAxisOriginWithFreeSpace:crossAxisFreeSpace] + self.superNode.paddingLeft;
}

- (CGFloat)crossAxisOriginTop {
    if (self.superNode.direction == DirectionColumn) { // not cross axis
        return 0;
    }
    
    CGFloat crossAxisFreeSpace = [self.superNode contentHeight] - [self boxHeight];
    return [self crossAxisOriginWithFreeSpace:crossAxisFreeSpace] + self.superNode.paddingTop;
}

- (CGFloat)mainAxisStartPositionWithFreeSpace:(CGFloat)freeSpace {
    if (self.superNode.justifyContent == JustifyContentFlexStart) {
        return 0;
    }
    
    if (self.superNode.justifyContent == JustifyContentCenter) {
        return freeSpace / 2;
    }
    
    if (self.superNode.justifyContent == JustifyContentFlexEnd) {
        return freeSpace;
    }
    
    if (self.superNode.justifyContent == JustifyContentSpaceAround) {
        return freeSpace / (self.superNode.nodes.count * 2);
    }
    
    if (self.superNode.justifyContent == JustifyContentSpaceBetween) {
        return 0;
    }
    
    return 0;
}

- (CGFloat)mainAxisStartPositionLeft {
    if (self.superNode.direction == DirectionColumn) { // not main axis
        return 0;
    }
    
    return [self mainAxisStartPositionWithFreeSpace:self.superNode.mainAxisTotalFreeWidth] + self.superNode.paddingLeft;
}

- (CGFloat)mainAxisStartPositionTop {
    if (self.superNode.direction == DirectionRow) { // not main axis
        return 0;
    }
    
    return [self mainAxisStartPositionWithFreeSpace:self.superNode.mainAxisTotalFreeHeight] + self.superNode.paddingTop;
}

- (CGFloat)mainAxisOffsetWithFreeSpace:(CGFloat)freeSpace {
    if (self.superNode.justifyContent == JustifyContentFlexStart) {
        return 0;
    }
    
    if (self.superNode.justifyContent == JustifyContentCenter) {
        return 0;
    }
    
    if (self.superNode.justifyContent == JustifyContentFlexEnd) {
        return 0;
    }
    
    if (self.superNode.justifyContent == JustifyContentSpaceAround) {
        return freeSpace / self.superNode.nodes.count;
    }
    
    if (self.superNode.justifyContent == JustifyContentSpaceBetween) {
        return freeSpace / (self.superNode.nodes.count - 1);
    }
    
    return 0;
}

- (CGFloat)mainAxisOffsetHorizontal {
    return [self mainAxisOffsetWithFreeSpace:self.superNode.mainAxisTotalFreeWidth];
}

- (CGFloat)mainAxisOffsetVertical {
    return [self mainAxisOffsetWithFreeSpace:self.superNode.mainAxisTotalFreeHeight];
}

- (CGFloat)originLeft {
    
    if (self.superNode.direction == DirectionRow) { // main axis
        if (self.previousSiblingNode) {
            return [self.previousSiblingNode rightBoundary] + [self mainAxisOffsetHorizontal];
        }
        
        return [self mainAxisStartPositionLeft];
    }
    
    if (self.superNode.direction == DirectionColumn) { // cross axis
        return [self crossAxisOriginLeft];
    }
    
    return 0;
}

- (CGFloat)originTop {
    
    if (self.superNode.direction == DirectionRow) { // cross axis
        return [self crossAxisOriginTop];
    }
    
    if (self.superNode.direction == DirectionColumn) { // main axis
        if (self.previousSiblingNode) {
            return [self.previousSiblingNode bottomBoundary] + [self mainAxisOffsetVertical];
        }
        
        return [self mainAxisStartPositionTop];
    }
    
    return 0;
}

- (CGFloat)preparedWidthWithNodes {
    
    if (self.direction == DirectionRow) { // main axis
        return self.totalNodePreparedBoxWidth;
    }
    
    if (self.direction == DirectionColumn) { // cross axis
        return self.maxNodePreparedBoxWidth;
    }
    
    return 0;
}

- (CGFloat)preparedHeightWithNodes {
    
    if (self.direction == DirectionRow) { // cross axis
        return self.maxNodePreparedBoxHeight;
    }
    
    if (self.direction == DirectionColumn) { // main axis
        return self.totalNodePreparedBoxHeight;
    }
    
    return 0;
}

- (CGFloat)calculateFrameLeft {
    return [self originLeft] + self.marginLeft;
}

- (CGFloat)calculateFrameTop {
    return [self originTop] + self.marginTop;
}

- (CGFloat)calculateFrameWidth {
    
    if (!self.superNode) { // root node
        CGFloat fullWidth = self.paddingLeft + self.width + self.paddingRight;
        if (fullWidth) {
            return fullWidth;
        }
        return self.containerWidth - self.marginLeft - self.marginRight;
    }
    
    if (self.superNode.direction == DirectionRow) { // main axis
        return self.preparedWidth + self.flexWidth;
    }
    
    if (self.superNode.direction == DirectionColumn) { // cross axis
        CGFloat contentWidth = self.width ? self.width : [self preparedWidthWithNodes];
        return self.paddingLeft + contentWidth + self.paddingRight;
    }
    
    return 0;
}

- (CGFloat)calculateFrameHeight {
    
    if (!self.superNode) { // root node
        CGFloat contentHeight = self.height ? self.height : [self preparedHeightWithNodes];
        return self.paddingTop + contentHeight + self.paddingBottom;
    }
    
    if (self.superNode.direction == DirectionRow) { // cross axis
        CGFloat contentHeight = self.height ? self.height : [self preparedHeightWithNodes];
        return self.paddingTop + contentHeight + self.paddingBottom;
    }
    
    if (self.superNode.direction == DirectionColumn) { // main axis
        return self.preparedHeight + self.flexHeight;
    }
    
    return 0;
}

#pragma mark - Delegate methods



#pragma mark - Setter



#pragma mark - Getter

- (BOOL)isRootNode {
    return !self.superNode;
}

- (CGRect)frame {
    CGFloat frameWidth = self.frameWidth > 0 ? self.frameWidth : 0;
    CGFloat frameHeight = self.frameHeight > 0 ? self.frameHeight : 0;
    return CGRectMake(self.frameLeft, self.frameTop, frameWidth, frameHeight);
}

- (NSMutableArray<OADLayout5 *> *)nodes {
    if (!_nodes) {
        _nodes = [NSMutableArray array];
    }
    return _nodes;
}

- (NSMutableDictionary<NSString *,OADLayout5 *> *)nodePool {
    if (!_nodePool) {
        _nodePool = [NSMutableDictionary dictionary];
    }
    return _nodePool;
}

- (OADLayout5 *)previousSiblingNode {
    if (!_previousSiblingNode) {
        NSInteger nodeIndex = [self.superNode.nodes indexOfObject:self];
        if (nodeIndex != NSNotFound && nodeIndex >= 1) {
            _previousSiblingNode = self.superNode.nodes[nodeIndex - 1];
        }
    }
    return _previousSiblingNode;
}

- (CGFloat)preparedWidth {
    if (!_preparedWidth) {
        CGFloat contentWidth = self.width ? self.width : [self preparedWidthWithNodes];
        _preparedWidth = self.paddingLeft + contentWidth + self.paddingRight;
    }
    return _preparedWidth;
}

- (CGFloat)preparedHeight {
    if (!_preparedHeight) {
        CGFloat contentHeight = self.height ? self.height : [self preparedHeightWithNodes];
        _preparedHeight = self.paddingTop + contentHeight + self.paddingBottom;
    }
    return _preparedHeight;
}

- (CGFloat)flexWidth {
    if (!_flexWidth) {
        CGFloat flexWidth = 0;
        
        if (self.superNode.direction == DirectionColumn) {
            flexWidth = 0;
        } else if (!self.grow) {
            flexWidth = 0;
        } else {
            flexWidth = self.superNode.mainAxisTotalFlexWidth * self.grow / self.superNode.totalNodeGrow;
        }
        
        _flexWidth = flexWidth;
    }
    return _flexWidth;
}

- (CGFloat)flexHeight {
    if (!_flexHeight) {
        CGFloat flexHeight = 0;
        
        if (self.superNode.direction == DirectionRow) {
            flexHeight = 0;
        } else if (!self.grow) {
            flexHeight = 0;
        } else {
            flexHeight = self.superNode.mainAxisTotalFlexHeight * self.grow / self.superNode.totalNodeGrow;
        }
        
        _flexHeight = flexHeight;
    }
    return _flexHeight;
}

- (CGFloat)mainAxisTotalFreeWidth {
    if (!_mainAxisTotalFreeWidth) {
        _mainAxisTotalFreeWidth = [self contentWidth] - self.totalNodeBoxWidth;
    }
    return _mainAxisTotalFreeWidth;
}

- (CGFloat)mainAxisTotalFreeHeight {
    if (!_mainAxisTotalFreeHeight) {
        _mainAxisTotalFreeHeight = [self contentHeight] - self.totalNodeBoxHeight;
    }
    return _mainAxisTotalFreeHeight;
}

- (CGFloat)mainAxisTotalFlexWidth {
    if (!_mainAxisTotalFlexWidth) {
        _mainAxisTotalFlexWidth = [self contentWidth] - self.totalNodePreparedBoxWidth;
    }
    return _mainAxisTotalFlexWidth;
}

- (CGFloat)mainAxisTotalFlexHeight {
    if (!_mainAxisTotalFlexHeight) {
        _mainAxisTotalFlexHeight = [self contentHeight] - self.totalNodePreparedBoxHeight;
    }
    return _mainAxisTotalFlexHeight;
}

- (CGFloat)totalNodePreparedBoxWidth {
    if (!_totalNodePreparedBoxWidth) {
        CGFloat totalNodePreparedBoxWidth = 0;
        for (OADLayout5 *node in self.nodes) {
            totalNodePreparedBoxWidth += [node preparedBoxWidth];
        }
        _totalNodePreparedBoxWidth = totalNodePreparedBoxWidth;
    }
    return _totalNodePreparedBoxWidth;
}

- (CGFloat)totalNodePreparedBoxHeight {
    if (!_totalNodePreparedBoxHeight) {
        CGFloat totalNodePreparedBoxHeight = 0;
        for (OADLayout5 *node in self.nodes) {
            totalNodePreparedBoxHeight += [node preparedBoxHeight];
        }
        _totalNodePreparedBoxHeight = totalNodePreparedBoxHeight;
    }
    return _totalNodePreparedBoxHeight;
}

- (CGFloat)totalNodeBoxWidth {
    if (!_totalNodeBoxWidth) {
        CGFloat totalNodeBoxWidth = 0;
        for (OADLayout5 *node in self.nodes) {
            totalNodeBoxWidth += [node boxWidth];
        }
        _totalNodeBoxWidth = totalNodeBoxWidth;
    }
    return _totalNodeBoxWidth;
}

- (CGFloat)totalNodeBoxHeight {
    if (!_totalNodeBoxHeight) {
        CGFloat totalNodeBoxHeight = 0;
        for (OADLayout5 *node in self.nodes) {
            totalNodeBoxHeight += [node boxHeight];
        }
        _totalNodeBoxHeight = totalNodeBoxHeight;
    }
    return _totalNodeBoxHeight;
}

- (CGFloat)maxNodePreparedBoxWidth {
    if (!_maxNodePreparedBoxWidth) {
        CGFloat maxNodePreparedBoxWidth = 0;
        for (OADLayout5 *node in self.nodes) {
            maxNodePreparedBoxWidth = MAX(maxNodePreparedBoxWidth, [node preparedBoxWidth]);
        }
        _maxNodePreparedBoxWidth = maxNodePreparedBoxWidth;
    }
    return _maxNodePreparedBoxWidth;
}

- (CGFloat)maxNodePreparedBoxHeight {
    if (!_maxNodePreparedBoxHeight) {
        CGFloat maxNodePreparedBoxHeight = 0;
        for (OADLayout5 *node in self.nodes) {
            maxNodePreparedBoxHeight = MAX(maxNodePreparedBoxHeight, [node preparedBoxHeight]);
        }
        _maxNodePreparedBoxHeight = maxNodePreparedBoxHeight;
    }
    return _maxNodePreparedBoxHeight;
}

- (CGFloat)frameLeft {
    if (!_frameLeft) {
        _frameLeft = [self calculateFrameLeft];
    }
    return _frameLeft;
}

- (CGFloat)frameTop {
    if (!_frameTop) {
        _frameTop = [self calculateFrameTop];
    }
    return _frameTop;
}

- (CGFloat)frameWidth {
    if (!_frameWidth) {
        _frameWidth = [self calculateFrameWidth];
    }
    return _frameWidth;
}

- (CGFloat)frameHeight {
    if (!_frameHeight) {
        _frameHeight = [self calculateFrameHeight];
    }
    return _frameHeight;
}

#pragma mark - Memory management methods

- (void)dealloc {
}

@end
